#include "WebServer.h"
#include "EmbeddedWebResources.h"
#include "core/Logger.h"
#include "service/DispatchService.h"
#include "service/DataCollector.h"
#include "service/DataUploader.h"
#include "device/DeviceManager.h"

#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <cstdlib>
#include <cstdint>
#include <sstream>
#include <iostream>
#include <cstring>
#include <algorithm>
#include <cctype>
#include <ctime>
#include <fstream>
#include <dirent.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <set>
#include <vector>
#include <netdb.h>
#include <fcntl.h>
#include <sys/select.h>
#include <thread>
#include <chrono>
#include <iomanip>
#include <cmath>
#include <mutex>
#include <condition_variable>
#include "utils/Json.h"
#include "protocol/mqtt/MqttClient.h"
#include <openssl/hmac.h>
#include <openssl/evp.h>

#ifdef __APPLE__
#include <mach/mach.h>
#include <sys/sysctl.h>
#endif

namespace ems {
namespace web {

namespace {

struct MemoryUsage {
    uint64_t totalBytes{0};
    uint64_t usedBytes{0};
    double percentage{0.0};
    bool valid{false};
};

static MemoryUsage queryMemoryUsage() {
    MemoryUsage info;
#if defined(__linux__)
    std::ifstream meminfo("/proc/meminfo");
    if (meminfo.is_open()) {
        std::string key;
        uint64_t value = 0;
        std::string unit;
        uint64_t memTotal = 0;
        uint64_t memAvailable = 0;
        uint64_t memFree = 0;
        uint64_t buffers = 0;
        uint64_t cached = 0;
        while (meminfo >> key >> value >> unit) {
            if (key == "MemTotal:") {
                memTotal = value * 1024ULL;
            } else if (key == "MemAvailable:") {
                memAvailable = value * 1024ULL;
            } else if (key == "MemFree:") {
                memFree = value * 1024ULL;
            } else if (key == "Buffers:") {
                buffers = value * 1024ULL;
            } else if (key == "Cached:") {
                cached = value * 1024ULL;
            }
        }
        if (memAvailable == 0 && memTotal > 0) {
            uint64_t reclaimable = buffers + cached;
            memAvailable = memFree + reclaimable;
            if (memAvailable > memTotal) {
                memAvailable = memFree; // fallback
            }
        }
        if (memTotal > 0) {
            info.totalBytes = memTotal;
            if (memAvailable <= memTotal) {
                info.usedBytes = memTotal - memAvailable;
            } else {
                info.usedBytes = memTotal;
            }
            info.valid = true;
        }
    }
#elif defined(__APPLE__)
    uint64_t total = 0;
    size_t len = sizeof(total);
    if (sysctlbyname("hw.memsize", &total, &len, nullptr, 0) == 0) {
        vm_statistics64_data_t vmstat{};
        mach_msg_type_number_t count = HOST_VM_INFO64_COUNT;
        kern_return_t kr = host_statistics64(mach_host_self(), HOST_VM_INFO64,
                                             reinterpret_cast<host_info64_t>(&vmstat), &count);
        if (kr == KERN_SUCCESS) {
            uint64_t pageSize = static_cast<uint64_t>(sysconf(_SC_PAGESIZE));
            uint64_t freeBytes = (static_cast<uint64_t>(vmstat.free_count) +
                                  static_cast<uint64_t>(vmstat.inactive_count)) * pageSize;
            if (total >= freeBytes) {
                info.totalBytes = total;
                info.usedBytes = total - freeBytes;
                info.valid = true;
            }
        }
    }
#endif
    if (info.valid && info.totalBytes > 0) {
        info.percentage = (static_cast<double>(info.usedBytes) / static_cast<double>(info.totalBytes)) * 100.0;
    }
    return info;
}

static double queryCpuUsagePercent() {
    double usage = 0.0;
#if defined(__linux__) || defined(__APPLE__)
    double loadAvg[3] = {0.0, 0.0, 0.0};
    if (getloadavg(loadAvg, 1) == 1) {
        unsigned int hwCores = std::thread::hardware_concurrency();
        if (hwCores == 0) {
            hwCores = 1;
        }
        double cores = static_cast<double>(hwCores);
        usage = (loadAvg[0] / cores) * 100.0;
        if (!std::isfinite(usage) || usage < 0.0) {
            usage = 0.0;
        }
        if (usage > 100.0) {
            usage = 100.0;
        }
    }
#endif
    return usage;
}

static std::string formatDouble(double value, int precision) {
    std::ostringstream oss;
    oss.setf(std::ios::fixed);
    oss << std::setprecision(precision) << value;
    return oss.str();
}

// 将紧凑JSON字符串格式化为缩进良好的多行文本
static std::string prettyJson(const std::string& src) {
    // 仅在明显是JSON对象/数组时处理
    auto it = std::find_if(src.begin(), src.end(), [](unsigned char ch){ return !std::isspace(ch); });
    if (it == src.end() || (*it != '{' && *it != '[')) {
        return src; // 非JSON，直接返回
    }

    std::string out;
    out.reserve(src.size() + src.size() / 4);
    const int indentSize = 2;
    int depth = 0;
    bool inString = false;
    bool escape = false;

    for (size_t i = 0; i < src.size(); ++i) {
        unsigned char ch = static_cast<unsigned char>(src[i]);

        if (escape) {
            out.push_back(ch);
            escape = false;
            continue;
        }
        if (ch == '\\') {
            out.push_back(ch);
            if (inString) escape = true;
            continue;
        }
        if (ch == '"') {
            out.push_back(ch);
            inString = !inString;
            continue;
        }

        if (inString) {
            out.push_back(ch);
            continue;
        }

        switch (ch) {
            case '{':
            case '[':
                out.push_back(ch);
                out.push_back('\n');
                depth++;
                out.append(depth * indentSize, ' ');
                break;
            case '}':
            case ']':
                out.push_back('\n');
                depth = std::max(0, depth - 1);
                out.append(depth * indentSize, ' ');
                out.push_back(ch);
                break;
            case ',':
                out.push_back(ch);
                out.push_back('\n');
                out.append(depth * indentSize, ' ');
                break;
            case ':':
                out.append(": ");
                break;
            default:
                if (!std::isspace(ch)) out.push_back(ch);
                break;
        }
    }

    return out;
}

static std::vector<std::string> listSerialPorts() {
    std::set<std::string> found;

#if defined(_WIN32)
    for (int index = 1; index <= 32; ++index) {
        found.insert("COM" + std::to_string(index));
    }
#else
    const std::vector<std::string> directories = { "/dev" };
    const std::vector<std::string> prefixes = {
        "ttyS", "ttyUSB", "ttyACM", "ttyAMA", "ttyXR", "ttyO",
        "tty.usb", "tty.Bluetooth", "ttyGS", "ttySTM", "ttyPS",
        "ttyPL", "ttyUL", "ttyX", "cu."
    };

    for (const auto& dirPath : directories) {
        DIR* dir = opendir(dirPath.c_str());
        if (!dir) {
            continue;
        }

        struct dirent* entry = nullptr;
        while ((entry = readdir(dir)) != nullptr) {
            if (!entry->d_name) {
                continue;
            }

            std::string name(entry->d_name);
            if (name.empty() || name[0] == '.') {
                continue;
            }

            bool matched = false;
            for (const auto& prefix : prefixes) {
                if (name.rfind(prefix, 0) == 0) {
                    matched = true;
                    break;
                }
            }
            if (!matched) {
                continue;
            }

            std::string fullPath = dirPath + "/" + name;
            struct stat pathStat{};
            if (::stat(fullPath.c_str(), &pathStat) != 0) {
                continue;
            }

            if (S_ISCHR(pathStat.st_mode)) {
                found.insert(fullPath);
            }
        }

        closedir(dir);
    }
#endif

    return std::vector<std::string>(found.begin(), found.end());
}

static std::string hexEncode(const unsigned char* data, size_t len) {
    static const char* hex = "0123456789abcdef";
    std::string out;
    out.resize(len * 2);
    for (size_t i = 0; i < len; ++i) {
        out[2*i]   = hex[(data[i] >> 4) & 0xF];
        out[2*i+1] = hex[data[i] & 0xF];
    }
    return out;
}

static std::string hmacSha256Hex(const std::string& key, const std::string& content) {
    unsigned char md[EVP_MAX_MD_SIZE];
    unsigned int md_len = 0;
    HMAC(EVP_sha256(), key.data(), static_cast<int>(key.size()),
         reinterpret_cast<const unsigned char*>(content.data()), content.size(), md, &md_len);
    return hexEncode(md, md_len);
}

static std::string randomDigits(int n) {
    std::string s; s.reserve(n);
    for (int i = 0; i < n; ++i) {
        int v = rand() % 10;
        s.push_back(static_cast<char>('0' + v));
    }
    return s;
}

static void appendEscaped(std::ostringstream& oss, const std::string& s) {
    for (char c : s) {
        switch (c) {
            case '"': oss << "\\\""; break;
            case '\\': oss << "\\\\"; break;
            case '\n': oss << " "; break;
            case '\r': break;
            case '\t': oss << " "; break;
            default: oss << c; break;
        }
    }
}
} // namespace

WebServer::WebServer(int port, const std::string& configRoot) 
    : port_(port),
      serverSocket_(-1),
      running_(false),
      shouldStop_(false),
      configRoot_(configRoot),
      dispatchService_(nullptr),
      startTime_(std::chrono::steady_clock::now()) {
}

WebServer::~WebServer() {
    stop();
}

void WebServer::setDispatchService(service::DispatchService* dispatchService) {
    dispatchService_ = dispatchService;
}

void WebServer::setDeviceManager(device::DeviceManager* deviceManager) {
    deviceManager_ = deviceManager;
}

void WebServer::setDataCollector(service::DataCollector* dataCollector) {
    dataCollector_ = dataCollector;
}

void WebServer::setDataUploader(service::DataUploader* dataUploader) {
    dataUploader_ = dataUploader;
}

bool WebServer::start() {
    if (running_) {
        LOG_WARN("Web server is already running");
        return true;
    }

    startTime_ = std::chrono::steady_clock::now();

    serverSocket_ = socket(AF_INET, SOCK_STREAM, 0);
    if (serverSocket_ == -1) {
        LOG_ERROR("Failed to create socket");
        return false;
    }

    // 允许端口重用
    int opt = 1;
    if (setsockopt(serverSocket_, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) == -1) {
        LOG_WARN("Failed to set socket options");
    }

    sockaddr_in serverAddr{};
    serverAddr.sin_family = AF_INET;
    serverAddr.sin_addr.s_addr = INADDR_ANY;
    serverAddr.sin_port = htons(port_);

    if (bind(serverSocket_, (sockaddr*)&serverAddr, sizeof(serverAddr)) == -1) {
        LOG_ERROR("Failed to bind to port " << port_);
        close(serverSocket_);
        return false;
    }

    if (listen(serverSocket_, 5) == -1) {
        LOG_ERROR("Failed to listen on port " << port_);
        close(serverSocket_);
        return false;
    }

    shouldStop_ = false;
    serverThread_ = std::thread(&WebServer::serverThread, this);
    
    LOG_INFO("Web server started on port " << port_);
    return true;
}

void WebServer::stop() {
    if (!running_) {
        return;
    }

    LOG_INFO("Stopping web server...");
    
    shouldStop_ = true;
    
    // 关闭服务器socket，这会导致accept()返回错误，从而退出服务器线程
    if (serverSocket_ != -1) {
        shutdown(serverSocket_, SHUT_RDWR);  // 优雅关闭socket
        close(serverSocket_);
        serverSocket_ = -1;
    }

    // 等待服务器线程结束
    if (serverThread_.joinable()) {
        serverThread_.join();
    }

    running_ = false;
    LOG_INFO("Web server stopped and port " << port_ << " released");
}

void WebServer::serverThread() {
    running_ = true;

    while (!shouldStop_) {
        sockaddr_in clientAddr{};
        socklen_t clientLen = sizeof(clientAddr);
        
        int clientSocket = accept(serverSocket_, (sockaddr*)&clientAddr, &clientLen);
        if (clientSocket == -1) {
            if (!shouldStop_) {
                LOG_ERROR("Failed to accept connection");
            }
            continue;
        }

        // 简化版：在主线程处理请求（生产环境应该用线程池）
        handleConnection(clientSocket);
        close(clientSocket);
    }

    running_ = false;
}

void WebServer::handleConnection(int clientSocket) {
    char buffer[4096]; // 减小请求缓冲区，只用于读取HTTP头
    int bytes = recv(clientSocket, buffer, sizeof(buffer) - 1, 0);
    
    if (bytes <= 0) {
        return;
    }

    buffer[bytes] = '\0';
    std::string request(buffer);
    // 预解析请求行，支持 SSE 流式响应
    {
        std::istringstream iss(request);
        std::string method, path, version;
        iss >> method >> path >> version;
        if (!method.empty()) {
            if (path.find("/api/mqtt/test/stream") == 0) {
                if (handleSseRequest(clientSocket, method, path, request)) {
                    // SSE 已直接写入并完成
                    return;
                }
            }
            if (path.find("/api/mqtt/console/stream") == 0) {
                if (handleConsoleSseRequest(clientSocket, method, path)) {
                    return; // 长连接已处理
                }
            }
        }
    }
    
    std::string response = handleRequest(request);
    
    // 分块发送大响应
    const char* data = response.c_str();
    size_t totalSize = response.length();
    size_t sentSize = 0;
    const size_t chunkSize = 8192;
    
    while (sentSize < totalSize) {
        size_t currentChunk = std::min(chunkSize, totalSize - sentSize);
        ssize_t sent = send(clientSocket, data + sentSize, currentChunk, 0);
        
        if (sent <= 0) {
            break; // 发送失败
        }
        
        sentSize += sent;
    }
}

// 处理 SSE 流式测试：GET /api/mqtt/test/stream?test_mode=xxx
bool WebServer::handleSseRequest(int clientSocket, const std::string& method,
                          const std::string& pathWithQuery, const std::string& rawRequest) {
    if (method != "GET") {
        const char* resp = "HTTP/1.1 405 Method Not Allowed\r\nContent-Length: 0\r\n\r\n";
        send(clientSocket, resp, (int)strlen(resp), 0);
        return true;
    }
    // 解析 query
    std::string path = pathWithQuery;
    std::string query;
    auto qpos = pathWithQuery.find('?');
    if (qpos != std::string::npos) {
        path = pathWithQuery.substr(0, qpos);
        query = pathWithQuery.substr(qpos + 1);
    }
    auto getQuery = [&](const std::string& key, const std::string& defVal) -> std::string {
        size_t start = 0;
        while (start < query.size()) {
            size_t amp = query.find('&', start);
            std::string pair = query.substr(start, amp == std::string::npos ? std::string::npos : amp - start);
            size_t eq = pair.find('=');
            if (eq != std::string::npos) {
                std::string k = pair.substr(0, eq);
                std::string v = pair.substr(eq + 1);
                if (k == key) return v;
            }
            if (amp == std::string::npos) break;
            start = amp + 1;
        }
        return defVal;
    };
    std::string testMode = getQuery("test_mode", "connect_publish");

    // 写入 SSE 响应头
    std::ostringstream hdr;
    hdr << "HTTP/1.1 200 OK\r\n"
        << "Content-Type: text/event-stream\r\n"
        << "Cache-Control: no-cache\r\n"
        << "Connection: keep-alive\r\n"
        << "Access-Control-Allow-Origin: *\r\n\r\n";
    std::string hs = hdr.str();
    send(clientSocket, hs.c_str(), (int)hs.size(), 0);

    auto sseSend = [&](const std::string& event, const std::string& json){
        std::ostringstream oss;
        oss << "event: " << event << "\n";
        oss << "data: " << json << "\n\n";
        std::string out = oss.str();
        send(clientSocket, out.c_str(), (int)out.size(), 0);
    };

    // 读取配置文件
    std::ifstream inFile(configRoot_ + "/mqtt.conf", std::ios::in | std::ios::binary);
    std::string host; int port = 0; bool useTls = false;
    std::string caFile, certFile, keyFile;
    std::string clientId, deviceSn, deviceType, username, password;
    int keepAlive = 60; bool cleanSession = true; int qos = 1; bool retained = false;
    std::string dataTopic = "up/gateway/report/data";
    std::string signMethod, deviceTypeSecret;
    if (inFile) {
        ems::utils::Json conf;
        if (conf.parse(inFile)) {
            auto broker = conf.getObject("broker");
            host = broker.getString("host", host);
            port = broker.getInt("port", port);
            useTls = broker.getBool("use_tls", useTls);
            caFile = broker.getString("ca_file", caFile);
            certFile = broker.getString("cert_file", certFile);
            keyFile = broker.getString("key_file", keyFile);
            auto client = conf.getObject("client");
            clientId = client.getString("client_id", clientId);
            deviceSn = client.getString("device_sn", deviceSn);
            deviceType = client.getString("device_type", deviceType);
            username = client.getString("username", username);
            password = client.getString("password", password);
            keepAlive = client.getInt("keep_alive", keepAlive);
            cleanSession = client.getBool("clean_session", cleanSession);
            qos = client.getInt("qos", qos);
            retained = client.getBool("retained", retained);
            auto topics = conf.getObject("topics");
            dataTopic = topics.getString("data_upload", dataTopic);
            auto security = conf.getObject("security");
            signMethod = security.getString("sign_method", signMethod);
            deviceTypeSecret = security.getString("device_type_secret", deviceTypeSecret);
        }
    }

    auto start = std::chrono::steady_clock::now();
    auto stepStart = start;
    auto durMs = [&](std::chrono::steady_clock::time_point t0){
        return (long) std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now() - t0).count();
    };

    // 发送 start 事件
    {
        std::ostringstream js; js << "{\"time\":\"" << getCurrentTimestamp() << "\",\"mode\":\"" << testMode << "\"}";
        sseSend("start", js.str());
    }

    if (host.empty() || port <= 0) {
        sseSend("step", "{\"name\":\"初始化\",\"status\":\"fail\",\"detail\":\"主机或端口缺失\",\"duration\":0}");
        sseSend("end", "{\"success\":false,\"message\":\"Host or port missing\"}");
        return true;
    }

    // 步骤：初始化
    sseSend("step", "{\"name\":\"初始化\",\"status\":\"ok\",\"detail\":\"创建MQTT客户端\",\"duration\":0}");

    using ems::protocol::MqttClient; using ems::protocol::MqttQoS;
    MqttClient client;

    if (!client.initialize(clientId.empty()? deviceSn : clientId, host, port)) {
        std::ostringstream js; js << "{\"name\":\"连接\",\"status\":\"fail\",\"detail\":\""; appendEscaped(js, client.getLastError()); js << "\",\"duration\":" << durMs(stepStart) << "}";
        sseSend("step", js.str());
        sseSend("end", "{\"success\":false,\"message\":\"MQTT init failed\"}");
        return true;
    }
    client.setKeepAlive(keepAlive);
    client.setCleanSession(cleanSession);
    if (!username.empty()) client.setCredentials(username, password);
    if (useTls) client.setTLS(caFile, certFile, keyFile);
    client.setMessageCallback([&](const std::string& t, const std::string& p){
        std::ostringstream js; js << "{\\\"dir\\\":\\\"recv\\\",\\\"topic\\\":\\\""; appendEscaped(js, t); js << "\\\",\\\"payload\\\":\\\""; appendEscaped(js, p); js << "\\\",\\\"time\\\":\\\""; appendEscaped(js, getCurrentTimestamp()); js << "\\\"}";
        sseSend("message", js.str());
    });

    stepStart = std::chrono::steady_clock::now();
    if (!client.connect()) {
        std::ostringstream js; js << "{\"name\":\"连接\",\"status\":\"fail\",\"detail\":\""; appendEscaped(js, client.getLastError()); js << "\",\"duration\":" << durMs(stepStart) << "}";
        sseSend("step", js.str());
        sseSend("end", "{\"success\":false,\"message\":\"CONNECT failed\"}");
        return true;
    }
    {
        std::ostringstream js; js << "{\"name\":\"连接\",\"status\":\"ok\",\"detail\":\"收到CONNACK\",\"duration\":" << durMs(stepStart) << "}";
        sseSend("step", js.str());
    }
    client.startLoop();

    auto doPublish = [&](const std::string& topic, const std::string& payload, const char* stepName){
        auto ps = std::chrono::steady_clock::now();
        bool ok = client.publish(topic, payload, qos==2?MqttQoS::QOS_2:(qos==1?MqttQoS::QOS_1:MqttQoS::QOS_0), false);
        std::ostringstream js;
        js << "{\"name\":\"" << stepName << "\",\"status\":\"" << (ok?"ok":"fail")
           << "\",\"detail\":\"主题："; appendEscaped(js, topic); js << "\",\"duration\":" << durMs(ps) << "}";
        sseSend("step", js.str());
        // 发送消息事件
        std::ostringstream ms; ms << "{\"dir\":\"send\",\"topic\":\""; appendEscaped(ms, topic); ms << "\",\"payload\":\""; appendEscaped(ms, payload); ms << "\",\"time\":\""; appendEscaped(ms, getCurrentTimestamp()); ms << "\"}";
        sseSend("message", ms.str());
        return ok;
    };

    bool success = false;
    if (testMode == "register_login" && signMethod == "hmacsha256" && !deviceTypeSecret.empty()) {
        // 动态注册
        std::string random = randomDigits(6);
        std::string content = std::string("authTyperegister") + "deviceSn" + deviceSn + "deviceType" + deviceType + "random" + random + "securemode1";
        std::string regPwd = hmacSha256Hex(deviceTypeSecret, content);
        std::string regCid = deviceType + "." + deviceSn + "|securemode=1,authType=register,random=" + random + ",signMethod=hmacsha256";
        std::string regUser = deviceSn + "&" + deviceType;
        client.stopLoop(); client.disconnect();
        client.initialize(regCid, host, port);
        client.setKeepAlive(keepAlive); client.setCleanSession(cleanSession); if (useTls) client.setTLS(caFile, certFile, keyFile);
        client.setCredentials(regUser, regPwd);
        std::string registerTopic = "/ext/" + deviceSn + "/register";
        std::string deviceSecret;
        std::mutex m2; std::condition_variable cv2; bool gotReg = false;
        client.setMessageCallback([&](const std::string& t, const std::string& p){
            // 推送所有接收的消息
            std::ostringstream ms; ms << "{\"dir\":\"recv\",\"topic\":\""; appendEscaped(ms, t); ms << "\",\"payload\":\""; appendEscaped(ms, p); ms << "\",\"time\":\""; appendEscaped(ms, getCurrentTimestamp()); ms << "\"}";
            sseSend("message", ms.str());
            // 解析注册下发
            if (t == registerTopic) {
                ems::utils::Json js; if (js.parse(p)) { deviceSecret = js.getString("deviceSecret"); }
                std::unique_lock<std::mutex> lk(m2); gotReg = true; cv2.notify_all();
            }
        });
        stepStart = std::chrono::steady_clock::now();
        if (!client.connect()) {
            std::ostringstream js; js << "{\"name\":\"动态注册\",\"status\":\"fail\",\"detail\":\""; appendEscaped(js, client.getLastError()); js << "\",\"duration\":" << durMs(stepStart) << "}";
            sseSend("step", js.str());
            sseSend("end", "{\"success\":false,\"message\":\"register connect failed\"}");
            return true;
        }
        client.startLoop();
        // 订阅所有下行，便于展示所有接收
        client.subscribe(std::string("down/gateway/") + (deviceSn.empty()? clientId : deviceSn) + "/#", MqttQoS::QOS_1);
        // 等待最多5秒注册下发
        {
            std::unique_lock<std::mutex> lk(m2);
            cv2.wait_for(lk, std::chrono::seconds(5));
        }
        if (deviceSecret.empty()) {
            std::ostringstream js; js << "{\"name\":\"动态注册\",\"status\":\"fail\",\"detail\":\"超时未收到设备密钥\",\"duration\":" << durMs(stepStart) << "}";
            sseSend("step", js.str());
            sseSend("end", "{\"success\":false,\"message\":\"register timeout\"}");
            return true;
        }
        {
            std::ostringstream js; js << "{\"name\":\"动态注册\",\"status\":\"ok\",\"detail\":\"已获取设备密钥\",\"duration\":" << durMs(stepStart) << "}";
            sseSend("step", js.str());
        }
        client.stopLoop(); client.disconnect();
        // 登录
        std::string random2 = randomDigits(6);
        std::string content2 = std::string("authTypelogin") + "deviceSn" + deviceSn + "deviceType" + deviceType + "random" + random2 + "securemode1";
        std::string loginPwd2 = hmacSha256Hex(deviceSecret, content2);
        std::string loginCid2 = deviceType + "." + deviceSn + "|securemode=1,authType=login,random=" + random2 + ",signMethod=hmacsha256";
        std::string loginUser2 = regUser;
        client.initialize(loginCid2, host, port);
        client.setKeepAlive(keepAlive); client.setCleanSession(cleanSession); if (useTls) client.setTLS(caFile, certFile, keyFile);
        client.setCredentials(loginUser2, loginPwd2);
        stepStart = std::chrono::steady_clock::now();
        bool conn2 = client.connect();
        {
            std::ostringstream js; js << "{\"name\":\"登录\",\"status\":\"" << (conn2?"ok":"fail") << "\",\"detail\":\""; appendEscaped(js, conn2?std::string("携带签名连接成功"):client.getLastError()); js << "\",\"duration\":" << durMs(stepStart) << "}";
            sseSend("step", js.str());
        }
        if (!conn2) { sseSend("end", "{\"success\":false,\"message\":\"login connect failed\"}"); return true; }
        client.startLoop();
        client.subscribe(std::string("down/gateway/") + (deviceSn.empty()? clientId : deviceSn) + "/#", MqttQoS::QOS_1);
        // 发布登录、订阅同步、发布同步响应
        {
            std::ostringstream lp; lp << "{\\\"cmdId\\\":101,\\\"seq\\\":0,\\\"gwId\\\":\\\"" << (deviceSn.empty()? clientId : deviceSn) << "\\\",\\\"errorCode\\\":0,\\\"data\\\":{\\\"deviceType\\\":" << (deviceType.empty()?"0":deviceType) << ",\\\"random\\\":1234}}";
            doPublish("up/gateway/login", lp.str(), "发布登录");
            client.subscribe(std::string("down/gateway/") + (deviceSn.empty()? clientId : deviceSn) + "/sync", MqttQoS::QOS_1);
            sseSend("step", "{\"name\":\"订阅同步下行\",\"status\":\"ok\",\"detail\":\"主题：down/gateway/.../sync\",\"duration\":0}");
            std::string sp = std::string("{\\\"cmdId\\\":201,\\\"gwId\\\":\\\"") + (deviceSn.empty()? clientId : deviceSn) + "\\\",\\\"ok\\\":true}";
            doPublish("up/gateway/sync/res", sp, "发布同步响应");
        }
        // 上报数据
        {
            auto now = std::chrono::system_clock::now();
            auto epoch = std::chrono::duration_cast<std::chrono::seconds>(now.time_since_epoch()).count();
            std::ostringstream data;
            data << "{   \\\"cmdId\\\": 312,   \\\"gwId\\\": \\\"" << (deviceSn.empty()? (clientId.empty()? "ems" : clientId) : deviceSn) << "\\\",   \\\"data\\\": {     \\\"time\\\": " << epoch << ",     \\\"dSn\\\": \\\"" << (deviceSn.empty()? (clientId.empty()? "ems" : clientId) : deviceSn) << "\\\",     \\\"events\\\": [ { \\\"key\\\": \\\"_test_ping\\\", \\\"value\\\": 1 } ]   } }";
            doPublish(dataTopic, data.str(), "发布数据");
        }
        client.stopLoop(); client.disconnect();
        success = true;
    } else {
        // 非注册路径：全流程或连接+上报
        if (testMode == "full") {
            std::ostringstream lp; lp << "{\\\"cmdId\\\":101,\\\"seq\\\":0,\\\"gwId\\\":\\\"" << (deviceSn.empty()? clientId : deviceSn) << "\\\",\\\"errorCode\\\":0,\\\"data\\\":{\\\"deviceType\\\":" << (deviceType.empty()?"0":deviceType) << ",\\\"random\\\":1234}}";
            doPublish("up/gateway/login", lp.str(), "发布登录");
            client.subscribe(std::string("down/gateway/") + (deviceSn.empty()? clientId : deviceSn) + "/sync", MqttQoS::QOS_1);
            sseSend("step", "{\"name\":\"订阅同步下行\",\"status\":\"ok\",\"detail\":\"主题：down/gateway/.../sync\",\"duration\":0}");
            std::string sp = std::string("{\\\"cmdId\\\":201,\\\"gwId\\\":\\\"") + (deviceSn.empty()? clientId : deviceSn) + "\\\",\\\"ok\\\":true}";
            doPublish("up/gateway/sync/res", sp, "发布同步响应");
        }
        if (testMode != "connect_only") {
            auto now2 = std::chrono::system_clock::now();
            auto epoch2 = std::chrono::duration_cast<std::chrono::seconds>(now2.time_since_epoch()).count();
            std::ostringstream data2;
            data2 << "{   \\\"cmdId\\\": 312,   \\\"gwId\\\": \\\"" << (deviceSn.empty()? (clientId.empty()? "ems" : clientId) : deviceSn) << "\\\",   \\\"data\\\": {     \\\"time\\\": " << epoch2 << ",     \\\"dSn\\\": \\\"" << (deviceSn.empty()? (clientId.empty()? "ems" : clientId) : deviceSn) << "\\\",     \\\"events\\\": [ { \\\"key\\\": \\\"_test_ping\\\", \\\"value\\\": 1 } ]   } }";
            doPublish(dataTopic, data2.str(), "发布数据");
        }
        client.stopLoop(); client.disconnect();
        success = true;
    }
    long totalMs = (long)std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now() - start).count();
    {
        std::ostringstream js; js << "{"
                                   << "\"success\":" << (success?"true":"false")
                                   << ",\"message\":\"" << (success?"连接并上报成功":"失败") << "\""
                                   << ",\"details\":{\"responseTime\":" << totalMs << "}}";
        sseSend("end", js.str());
    }
    return true;
}

// 长连接实时控制台：连接 MQTT 并订阅主题，持续通过 SSE 推送消息
bool WebServer::handleConsoleSseRequest(int clientSocket, const std::string& method,
                                 const std::string& pathWithQuery) {
    if (method != "GET") {
        const char* resp = "HTTP/1.1 405 Method Not Allowed\r\nContent-Length: 0\r\n\r\n";
        send(clientSocket, resp, (int)strlen(resp), 0);
        return true;
    }
    // 解析 query
    std::string path = pathWithQuery;
    std::string query;
    auto qpos = pathWithQuery.find('?');
    if (qpos != std::string::npos) {
        path = pathWithQuery.substr(0, qpos);
        query = pathWithQuery.substr(qpos + 1);
    }
    auto getQuery = [&](const std::string& key, const std::string& defVal) -> std::string {
        size_t start = 0;
        while (start < query.size()) {
            size_t amp = query.find('&', start);
            std::string pair = query.substr(start, amp == std::string::npos ? std::string::npos : amp - start);
            size_t eq = pair.find('=');
            if (eq != std::string::npos) {
                std::string k = pair.substr(0, eq);
                std::string v = pair.substr(eq + 1);
                if (k == key) return v;
            }
            if (amp == std::string::npos) break;
            start = amp + 1;
        }
        return defVal;
    };
    std::string topicsCsv = getQuery("topics", "");

    // 写入 SSE 响应头
    {
        std::ostringstream hdr;
        hdr << "HTTP/1.1 200 OK\r\n"
            << "Content-Type: text/event-stream\r\n"
            << "Cache-Control: no-cache\r\n"
            << "Connection: keep-alive\r\n"
            << "Access-Control-Allow-Origin: *\r\n\r\n";
        std::string hs = hdr.str();
        send(clientSocket, hs.c_str(), (int)hs.size(), 0);
    }

    auto sseSend = [&](const std::string& event, const std::string& json){
        std::ostringstream oss;
        oss << "event: " << event << "\n";
        oss << "data: " << json << "\n\n";
        std::string out = oss.str();
        send(clientSocket, out.c_str(), (int)out.size(), 0);
    };

    // 读取配置文件
    std::ifstream inFile(configRoot_ + "/mqtt.conf", std::ios::in | std::ios::binary);
    std::string host; int port = 0; bool useTls = false;
    std::string caFile, certFile, keyFile;
    std::string clientId, deviceSn, deviceType, username, password;
    int keepAlive = 60; bool cleanSession = true; int qos = 1;
    if (inFile) {
        ems::utils::Json conf;
        if (conf.parse(inFile)) {
            auto broker = conf.getObject("broker");
            host = broker.getString("host", host);
            port = broker.getInt("port", port);
            useTls = broker.getBool("use_tls", useTls);
            caFile = broker.getString("ca_file", caFile);
            certFile = broker.getString("cert_file", certFile);
            keyFile = broker.getString("key_file", keyFile);
            auto client = conf.getObject("client");
            clientId = client.getString("client_id", clientId);
            deviceSn = client.getString("device_sn", deviceSn);
            deviceType = client.getString("device_type", deviceType);
            username = client.getString("username", username);
            password = client.getString("password", password);
            keepAlive = client.getInt("keep_alive", keepAlive);
            cleanSession = client.getBool("clean_session", cleanSession);
            qos = client.getInt("qos", qos);
        }
    }
    if (host.empty() || port <= 0) {
        sseSend("end", "{\"error\":\"Host or port missing\"}");
        return true;
    }

    // 主题集合
    std::vector<std::string> topics;
    if (!topicsCsv.empty()) {
        size_t pos = 0;
        while (pos < topicsCsv.size()) {
            size_t comma = topicsCsv.find(',', pos);
            std::string t = topicsCsv.substr(pos, comma == std::string::npos ? std::string::npos : comma - pos);
            // decode %2F 等（简单替换）
            std::replace(t.begin(), t.end(), '+', ' ');
            topics.push_back(t);
            if (comma == std::string::npos) break;
            pos = comma + 1;
        }
    } else {
        // 默认订阅：下发与上行常用主题
        std::string downPrefix = std::string("down/gateway/") + (deviceSn.empty()? (clientId.empty()? "" : clientId) : deviceSn) + "/#";
        topics.push_back("up/gateway/#");
        topics.push_back(downPrefix);
    }

    using ems::protocol::MqttClient; using ems::protocol::MqttQoS;
    MqttClient client;
    client.setKeepAlive(keepAlive);
    client.setCleanSession(cleanSession);
    if (!username.empty()) client.setCredentials(username, password);
    if (useTls) client.setTLS(caFile, certFile, keyFile);

    if (!client.initialize(clientId.empty()? (deviceSn.empty()? "ems-web-console" : deviceSn) : clientId, host, port)) {
        sseSend("end", std::string("{\"error\":\"") + client.getLastError() + "\"}");
        return true;
    }
    std::atomic<bool> connected{false};
    client.setConnectCallback([&](bool ok){ connected = ok; });
    client.setMessageCallback([&](const std::string& t, const std::string& p){
        std::ostringstream js; js << "{\"dir\":\"recv\",\"topic\":\""; appendEscaped(js, t); js << "\",\"payload\":\""; appendEscaped(js, p); js << "\"}";
        sseSend("message", js.str());
    });

    if (!client.connect()) {
        sseSend("end", std::string("{\"error\":\"CONNECT failed: ") + client.getLastError() + "\"}");
        return true;
    }
    client.startLoop();
    sseSend("status", "\"connected\"");
    for (const auto& t : topics) {
        client.subscribe(t, qos==2?MqttQoS::QOS_2:(qos==1?MqttQoS::QOS_1:MqttQoS::QOS_0));
    }

    // 简单心跳，直到对端断开（send 失败将由内核触发对端关闭；这里定期发送可见的注释行）
    for (int i = 0; i < 24*60*60; ++i) { // 最长保持 24h
        std::this_thread::sleep_for(std::chrono::seconds(10));
        sseSend("status", "\"ping\"");
        // 若需要可检查 connected 标志
        if (!connected) break;
        if (shouldStop_) break;
    }

    client.stopLoop();
    client.disconnect();
    sseSend("end", "\"closed\"");
    return true;
}

std::string WebServer::handleRequest(const std::string& request) {
    std::istringstream iss(request);
    std::string method, path, version;
    iss >> method >> path >> version;

    // Allow GET/PUT/POST for APIs, OPTIONS for CORS preflight
    if (method != "GET" && method != "PUT" && method != "POST" && method != "OPTIONS") {
        return "HTTP/1.1 405 Method Not Allowed\r\n"
               "Content-Length: 0\r\n"
               "\r\n";
    }

    // 默认路径
    if (path == "/" || path.empty()) {
        path = "/index.html";
    }

    // 内置调试页：/mqtt-test 使用后端实际连接MQTT，并通过SSE实时回显
    if (path == "/mqtt-test") {
        const char* html =
            "HTTP/1.1 200 OK\r\n"
            "Content-Type: text/html; charset=utf-8\r\n"
            "Cache-Control: no-store\r\n"
            "Pragma: no-cache\r\n"
            "\r\n"
            "<!doctype html><html lang=\"zh-CN\"><head><meta charset=\"utf-8\"/>"
            "<meta name=\"viewport\" content=\"width=device-width,initial-scale=1\"/>"
            "<title>MQTT 后端联测（SSE 实时日志）</title>"
            "<style>body{font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Arial,Helvetica,sans-serif;margin:16px;}"
            "h1{margin:0 0 12px;}fieldset{border:1px solid #ddd;padding:12px;margin:12px 0;}legend{padding:0 6px;font-weight:600;}"
            "button{margin-right:8px;padding:6px 10px;}#log{white-space:pre-wrap;background:#0b1221;color:#d1e3ff;min-height:260px;padding:10px;border-radius:8px;overflow:auto;}"
            ".ok{color:#9be59b}.warn{color:#ffd27f}.err{color:#ff9b9b}.mono{font-family:ui-monospace,Menlo,Consolas,monospace}</style></head><body>"
            "<h1>MQTT 后端联测</h1>"
            "<p>说明：不在浏览器内连 MQTT。点击下方按钮后，后端读取 <code class=\"mono\">config/mqtt.conf</code> 实际连接与上报，通过 SSE 实时回显过程。</p>"
            "<fieldset><legend>测试动作</legend>"
            "<button id=\"btnConnect\">仅连接(connect_only)</button>"
            "<button id=\"btnConnPub\">连接并上报(connect_publish)</button>"
            "<button id=\"btnRegLogin\">动态注册+登录(register_login)</button>"
            "<button id=\"btnStop\">停止</button>"
            "</fieldset>"
            "<fieldset><legend>实时日志</legend>"
            "<div id=\"log\" class=\"mono\"></div>"
            "</fieldset>"
            "<script>(function(){\n"
            "const logEl=document.getElementById('log');\n"
            "let es=null;\n"
            "function log(line,cls){const ts=new Date().toISOString();const c=cls?(' '+cls):'';logEl.innerHTML+=`<div class=\\\"${c}\\\">[${ts}] ${line}</div>`;logEl.scrollTop=logEl.scrollHeight;}\n"
            "function start(mode){stop();log('开始: '+mode);es=new EventSource('/api/mqtt/test/stream?test_mode='+encodeURIComponent(mode));\n"
            "es.addEventListener('start',ev=>{log('开始执行: '+ev.data)});\n"
            "es.addEventListener('step',ev=>{try{const o=JSON.parse(ev.data);log('步骤['+o.name+'] '+o.status+' - '+(o.detail||'')+' ('+o.duration+'ms)',o.status==='ok'?'ok':(o.status==='fail'?'err':''));}catch{log('步骤: '+ev.data)});\n"
            "es.addEventListener('message',ev=>{try{const o=JSON.parse(ev.data);const dir=o.dir==='send'?'→':'←';log(dir+' '+o.topic+'\\n'+o.payload);}catch{log('消息: '+ev.data)}});\n"
            "es.addEventListener('end',ev=>{log('结束: '+ev.data,'warn');stop();});\n"
            "es.onerror=function(){log('SSE 连接异常','err');};}\n"
            "function stop(){if(es){es.close();es=null;log('已停止','warn');}}\n"
            "document.getElementById('btnConnect').onclick=()=>start('connect_only');\n"
            "document.getElementById('btnConnPub').onclick=()=>start('connect_publish');\n"
            "document.getElementById('btnRegLogin').onclick=()=>start('register_login');\n"
            "document.getElementById('btnStop').onclick=()=>stop();\n"
            "})();</script></body></html>";
        return std::string(html);
    }

    if (path == "/mqtt-console") {
        const char* html =
            "HTTP/1.1 200 OK\r\n"
            "Content-Type: text/html; charset=utf-8\r\n"
            "Cache-Control: no-store\r\n\r\n"
            "<!doctype html><html lang=\"zh-CN\"><head><meta charset=\"utf-8\"/>"
            "<meta name=\"viewport\" content=\"width=device-width,initial-scale=1\"/>"
            "<title>MQTT 消息控制台（实时）</title>"
            "<style>body{font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Arial,Helvetica,sans-serif;margin:16px;}"
            "label{display:inline-block;min-width:96px;margin:6px 0;}input{padding:6px;width:360px;max-width:80vw;}button{padding:6px 10px;margin-left:8px;}"
            "#log{white-space:pre-wrap;background:#0b1221;color:#d1e3ff;min-height:360px;padding:10px;border-radius:8px;overflow:auto;margin-top:12px;}"
            ".ok{color:#9be59b}.warn{color:#ffd27f}.err{color:#ff9b9b}.mono{font-family:ui-monospace,Menlo,Consolas,monospace}</style></head><body>"
            "<h1>MQTT 消息控制台</h1>"
            "<p>后端实际连接 MQTT 并订阅主题，消息实时推送到此处显示。默认订阅 down/gateway/${deviceSn}/#、up/gateway/#。可自定义订阅多个主题（逗号分隔）。</p>"
            "<div><label>订阅主题</label><input id=\"topics\" value=\"\" placeholder=\"topic1,topic2,down/gateway/${deviceSn}/#\"/>"
            "<button id=\"btnStart\">开始</button><button id=\"btnStop\">停止</button></div>"
            "<div id=\"log\" class=\"mono\"></div>"
            "<script>(function(){\n"
            "let es=null;const logEl=document.getElementById('log');\n"
            "function log(line,cls){const ts=new Date().toISOString();logEl.innerHTML+=`<div class=\\\"${cls||''}\\\">[${ts}] ${line}</div>`;logEl.scrollTop=logEl.scrollHeight;}\n"
            "function start(){stop();const qs=new URLSearchParams();const t=document.getElementById('topics').value.trim();if(t)qs.set('topics',t);es=new EventSource('/api/mqtt/console/stream?'+qs.toString());\n"
            "es.addEventListener('status',ev=>{log('状态: '+ev.data,'warn')});\n"
            "es.addEventListener('message',ev=>{try{const o=JSON.parse(ev.data);const dir=o.dir==='send'?'→':'←';log(dir+' '+o.topic+'\\n'+o.payload);}catch{log('消息: '+ev.data)}});\n"
            "es.addEventListener('end',ev=>{log('结束: '+ev.data,'warn');stop();});\n"
            "es.onerror=function(){log('SSE 错误','err');};}\n"
            "function stop(){if(es){es.close();es=null;log('已停止','warn');}}\n"
            "document.getElementById('btnStart').onclick=start;\n"
            "document.getElementById('btnStop').onclick=stop;\n"
            "})();</script></body></html>";
        return std::string(html);
    }
    
    // 处理WebSocket请求
    if (path == "/ws") {
        return handleWebSocketRequest(request);
    }
    
    // 处理API请求
    if (path.find("/api/") == 0) {
        return handleApiRequest(method, path, request);
    }

    // 获取资源内容
    std::string content = getResource(path);
    if (content.empty()) {
        // SPA 路由回退：当访问诸如 /mqtt-config 这类前端路由时，
        // 没有对应的静态资源，应回退到 index.html，由前端路由接管。
        bool looksLikeSpaRoute = (path.find('.') == std::string::npos);
        if (looksLikeSpaRoute) {
            std::string indexHtml = getResource("/index.html");
            if (!indexHtml.empty()) {
                std::ostringstream response;
                response << "HTTP/1.1 200 OK\r\n"
                         << "Content-Type: text/html\r\n"
                         << "Cache-Control: no-store, no-cache, must-revalidate\r\n"
                         << "Pragma: no-cache\r\n"
                         << "Expires: 0\r\n"
                         << "Content-Length: " << indexHtml.length() << "\r\n\r\n"
                         << indexHtml;
                return response.str();
            }
        }

        return "HTTP/1.1 404 Not Found\r\n"
               "Content-Type: text/html\r\n"
               "Content-Length: 47\r\n"
               "\r\n"
               "<html><body><h1>404 Not Found</h1></body></html>";
    }

    std::string mimeType = getMimeType(path);
    std::ostringstream response;
    // 缓存策略：
    // - HTML 不缓存，避免旧入口文件引用过期的 chunk
    // - 其他静态资源（js/css/img 等）使用长期缓存（基于文件名 hash）
    bool isHtml = false;
    if (!path.empty()) {
        // 粗略判断是否为 HTML
        auto pos = path.rfind('.');
        if (pos != std::string::npos) {
            std::string ext = path.substr(pos);
            std::transform(ext.begin(), ext.end(), ext.begin(), ::tolower);
            isHtml = (ext == ".html");
        }
    }

    response << "HTTP/1.1 200 OK\r\n"
             << "Content-Type: " << mimeType << "\r\n"
             << "Content-Length: " << content.length() << "\r\n";

    if (isHtml) {
        // 禁止缓存 HTML，确保页面总是获取到最新入口资源
        response << "Cache-Control: no-store, no-cache, must-revalidate\r\n"
                 << "Pragma: no-cache\r\n"
                 << "Expires: 0\r\n";
    } else {
        // 其他静态资源采用长期缓存（前端构建产物带 hash）
        response << "Cache-Control: public, max-age=31536000, immutable\r\n";
    }

    response << "\r\n" << content;

    return response.str();
}

std::string WebServer::getResource(const std::string& path) {
    // 使用嵌入资源的查找函数
    const EmbeddedResource* resource = find_resource(path);
    if (resource) {
        return std::string(
            reinterpret_cast<const char*>(resource->data),
            resource->size
        );
    }
    
    return "";
}

std::string WebServer::getMimeType(const std::string& path) {
    // 使用嵌入资源的MIME类型
    const EmbeddedResource* resource = find_resource(path);
    if (resource) {
        return resource->mime_type;
    }
    
    // 简单的MIME类型推断
    if (path.find(".html") != std::string::npos) return "text/html";
    if (path.find(".css") != std::string::npos) return "text/css";
    if (path.find(".js") != std::string::npos) return "application/javascript";
    if (path.find(".json") != std::string::npos) return "application/json";
    if (path.find(".png") != std::string::npos) return "image/png";
    if (path.find(".jpg") != std::string::npos || path.find(".jpeg") != std::string::npos) return "image/jpeg";
    if (path.find(".svg") != std::string::npos) return "image/svg+xml";
    
    return "application/octet-stream";
}

std::string WebServer::handleApiRequest(const std::string& method,
                                        const std::string& path,
                                        const std::string& rawRequest) {
    LOG_INFO("API Request: " << method << " " << path);
    
    // Basic CORS preflight
    if (method == "OPTIONS") {
        std::ostringstream response;
        response << "HTTP/1.1 204 No Content\r\n"
                 << "Access-Control-Allow-Origin: *\r\n"
                 << "Access-Control-Allow-Methods: GET, POST, PUT, OPTIONS\r\n"
                 << "Access-Control-Allow-Headers: Content-Type\r\n"
                 << "Content-Length: 0\r\n\r\n";
        return response.str();
    }

    // Helper to parse body from raw request
    auto extractBody = [](const std::string& req) -> std::string {
        const std::string sep = "\r\n\r\n";
        auto pos = req.find(sep);
        if (pos == std::string::npos) return std::string();
        return req.substr(pos + sep.size());
    };

    // /api/config/mqtt -> GET returns config/mqtt.conf JSON, PUT writes it
    if (path == "/api/config/mqtt") {
        if (method == "GET") {
            // Read file content
            std::ifstream inFile(configRoot_ + "/mqtt.conf", std::ios::in | std::ios::binary);
            std::string content;
            if (inFile) {
                std::ostringstream ss;
                ss << inFile.rdbuf();
                content = ss.str();
            } else {
                // Fallback: try mqtt.json if mqtt.conf missing
                std::ifstream altFile(configRoot_ + "/mqtt.json", std::ios::in | std::ios::binary);
                if (altFile) {
                    std::ostringstream ss;
                    ss << altFile.rdbuf();
                    content = ss.str();
                } else {
                    content = "{}";
                }
            }

            // 为了便于可读性，返回格式化后的JSON
            content = prettyJson(content);

            std::ostringstream response;
            response << "HTTP/1.1 200 OK\r\n"
                     << "Content-Type: application/json\r\n"
                     << "Access-Control-Allow-Origin: *\r\n"
                     << "Content-Length: " << content.length() << "\r\n\r\n"
                     << content;
            return response.str();
        } else if (method == "PUT") {
            std::string body = extractBody(rawRequest);
            LOG_INFO("[WebServer] PUT /api/config/devices received, payload size=" << body.size());
            // Very basic validation: must look like JSON object
            auto firstBrace = body.find('{');
            auto lastBrace = body.rfind('}');
            if (firstBrace == std::string::npos || lastBrace == std::string::npos || lastBrace < firstBrace) {
                LOG_WARN("[WebServer] Invalid JSON payload when updating devices.json");
                std::string msg = "{\"error\":\"Invalid JSON payload\"}";
                std::ostringstream response;
                response << "HTTP/1.1 400 Bad Request\r\n"
                         << "Content-Type: application/json\r\n"
                         << "Access-Control-Allow-Origin: *\r\n"
                         << "Content-Length: " << msg.length() << "\r\n\r\n"
                         << msg;
                return response.str();
            }

            // Write to mqtt.conf
            // 先格式化再写入，确保文件可读
            std::string pretty = prettyJson(body);

            std::ofstream outFile(configRoot_ + "/mqtt.conf", std::ios::out | std::ios::binary | std::ios::trunc);
            if (!outFile) {
                std::string msg = "{\"error\":\"Failed to open mqtt.conf for writing\"}";
                std::ostringstream response;
                response << "HTTP/1.1 500 Internal Server Error\r\n"
                         << "Content-Type: application/json\r\n"
                         << "Access-Control-Allow-Origin: *\r\n"
                         << "Content-Length: " << msg.length() << "\r\n\r\n"
                         << msg;
                return response.str();
            }
            outFile.write(pretty.data(), static_cast<std::streamsize>(pretty.size()));
            outFile.close();

            std::string msg = "{\"message\":\"MQTT配置更新成功\"}";
            std::ostringstream response;
            response << "HTTP/1.1 200 OK\r\n"
                     << "Content-Type: application/json\r\n"
                     << "Access-Control-Allow-Origin: *\r\n"
                     << "Content-Length: " << msg.length() << "\r\n\r\n"
                     << msg;
            return response.str();
        }
    }

    // /api/mqtt/test -> POST
    // 默认：进行实际MQTT连接，并尝试发送一条数据上报（QoS基于配置），返回连接与发布结果
    if (path == "/api/mqtt/test") {
        if (method != "POST") {
            std::string msg = "{\"error\":\"Method not allowed\"}";
            std::ostringstream response;
            response << "HTTP/1.1 405 Method Not Allowed\r\n"
                     << "Content-Type: application/json\r\n"
                     << "Access-Control-Allow-Origin: *\r\n"
                     << "Content-Length: " << msg.length() << "\r\n\r\n"
                     << msg;
            return response.str();
        }

        std::string body = extractBody(rawRequest);
        ems::utils::Json posted;
        std::string testMode = "connect_publish"; // connect_only | connect_publish | full | register_login
        std::string host; int port = 0; bool useTls = false;
        std::string caFile, certFile, keyFile;
        std::string clientId, deviceSn, deviceType, username, password;
        int keepAlive = 60; bool cleanSession = true; int qos = 1; bool retained = false;
        std::string dataTopic = "up/gateway/report/data";
        // security for dynamic register/login
        std::string signMethod;
        std::string deviceTypeSecret;

        if (!body.empty() && posted.parse(body)) {
            testMode = posted.getString("test_mode", testMode);
            auto broker = posted.getObject("broker");
            host = broker.getString("host", "");
            port = broker.getInt("port", 0);
            useTls = broker.getBool("use_tls", false);
            caFile = broker.getString("ca_file");
            certFile = broker.getString("cert_file");
            keyFile = broker.getString("key_file");

            auto client = posted.getObject("client");
            clientId = client.getString("client_id");
            deviceSn = client.getString("device_sn");
            deviceType = client.getString("device_type");
            username = client.getString("username");
            password = client.getString("password");
            keepAlive = client.getInt("keep_alive", keepAlive);
            cleanSession = client.getBool("clean_session", cleanSession);
            qos = client.getInt("qos", qos);
            retained = client.getBool("retained", retained);

            auto topics = posted.getObject("topics");
            dataTopic = topics.getString("data_upload", dataTopic);
            auto security = posted.getObject("security");
            signMethod = security.getString("sign_method");
            deviceTypeSecret = security.getString("device_type_secret");
        }

        // fallback to file if necessary
        if (host.empty() || port <= 0) {
            std::ifstream inFile(configRoot_ + "/mqtt.conf", std::ios::in | std::ios::binary);
            if (inFile) {
                ems::utils::Json conf;
                if (conf.parse(inFile)) {
                    auto broker = conf.getObject("broker");
                    host = broker.getString("host", host);
                    port = broker.getInt("port", port);
                    useTls = broker.getBool("use_tls", useTls);
                    caFile = broker.getString("ca_file", caFile);
                    certFile = broker.getString("cert_file", certFile);
                    keyFile = broker.getString("key_file", keyFile);

                    auto client = conf.getObject("client");
                    clientId = client.getString("client_id", clientId);
                    deviceSn = client.getString("device_sn", deviceSn);
                    deviceType = client.getString("device_type", deviceType);
                    username = client.getString("username", username);
                    password = client.getString("password", password);
                    keepAlive = client.getInt("keep_alive", keepAlive);
                    cleanSession = client.getBool("clean_session", cleanSession);
                    qos = client.getInt("qos", qos);
                    retained = client.getBool("retained", retained);

                    auto topics = conf.getObject("topics");
                    dataTopic = topics.getString("data_upload", dataTopic);
                    auto security = conf.getObject("security");
                    signMethod = security.getString("sign_method", signMethod);
                    deviceTypeSecret = security.getString("device_type_secret", deviceTypeSecret);
                }
            }
        }

        auto start = std::chrono::steady_clock::now();
        bool connected = false;
        bool hadConnected = false;
        bool published = false;
        std::string err;
        // timeline 用于前端时间线展示（中文）
        std::ostringstream timeline;
        // messages 用于记录发送/接收的MQTT消息
        std::ostringstream msglog;
        auto pushMsg = [&](const std::string& dir, const std::string& topic, const std::string& payload){
            if (msglog.tellp() > 0) msglog << ",";
            msglog << "{\"dir\":\"" << dir << "\",\"topic\":\""; appendEscaped(msglog, topic);
            msglog << "\",\"payload\":\""; appendEscaped(msglog, payload);
            msglog << "\",\"time\":\""; appendEscaped(msglog, getCurrentTimestamp()); msglog << "\"}";
        };
        auto pushStep = [&](const std::string& name, const std::string& status, const std::string& detail, long durMs){
            if (timeline.tellp() > 0) timeline << ",";
            timeline << "{\"name\":\"" << name << "\",\"status\":\"" << status << "\",\"detail\":\"";
            for (char c : detail) { if (c=='"') timeline << '\\' << '"'; else if (c=='\n') timeline << ' '; else timeline << c; }
            timeline << "\",\"duration\":" << durMs << "}";
        };

        if (!host.empty() && port > 0) {
            ems::protocol::MqttClient client;
            auto stepStart = std::chrono::steady_clock::now();
            if (client.initialize(clientId.empty() ? deviceSn : clientId, host, port)) {
                client.setKeepAlive(keepAlive);
                client.setCleanSession(cleanSession);
                if (!username.empty()) {
                    client.setCredentials(username, password);
                }
                if (useTls) {
                    client.setTLS(caFile, certFile, keyFile);
                }
                pushStep("初始化", "ok", "创建MQTT客户端", (long)std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now()-stepStart).count());
                std::mutex mtx; std::condition_variable cv;
                bool acked = false; int ackId = 0;
                client.setConnectCallback([&](bool ok){
                    std::unique_lock<std::mutex> lk(mtx);
                    connected = ok;
                    if (ok) hadConnected = true;
                    cv.notify_all();
                });
                client.setPublishCallback([&](int mid, bool ok){
                    std::unique_lock<std::mutex> lk(mtx);
                    acked = ok; ackId = mid;
                    if (ok) published = true;
                    cv.notify_all();
                });
                client.setMessageCallback([&](const std::string& t, const std::string& p){
                    pushMsg("recv", t, p);
                });

                stepStart = std::chrono::steady_clock::now();
                if (!client.connect()) {
                    err = client.getLastError();
                    pushStep("连接", "fail", err, (long)std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now()-stepStart).count());
                } else {
                    pushStep("连接", "ok", "收到CONNACK", (long)std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now()-stepStart).count());
                    connected = true; // connect() 已完成阻塞等待并返回成功
                    hadConnected = true;
                    client.startLoop();
                    {
                        std::unique_lock<std::mutex> lk(mtx);
                        cv.wait_for(lk, std::chrono::milliseconds(100));
                    }
                    if (connected) {
                        // Build a minimal data payload per docs
                        auto now = std::chrono::system_clock::now();
                        auto epoch = std::chrono::duration_cast<std::chrono::seconds>(now.time_since_epoch()).count();
                        std::ostringstream data;
                        data << "{\n"
                             << "  \"cmdId\": 312,\n"
                             << "  \"gwId\": \"" << (deviceSn.empty()? (clientId.empty()? "ems" : clientId) : deviceSn) << "\",\n"
                             << "  \"data\": {\n"
                             << "    \"time\": " << epoch << ",\n"
                             << "    \"dSn\": \"" << (deviceSn.empty()? (clientId.empty()? "ems" : clientId) : deviceSn) << "\",\n"
                             << "    \"events\": [ { \"key\": \"_test_ping\", \"value\": 1 } ]\n"
                             << "  }\n"
                             << "}";

                        ems::protocol::MqttQoS q = ems::protocol::MqttQoS::QOS_1;
                        if (qos == 0) q = ems::protocol::MqttQoS::QOS_0; else if (qos == 2) q = ems::protocol::MqttQoS::QOS_2;

                        if (testMode == "connect_only") {
                            pushStep("发布数据", "skip", "模式为仅连接，跳过", 0);
                        } else {
                            auto pubStart = std::chrono::steady_clock::now();
                            bool pubOk = client.publish(dataTopic, data.str(), q, retained);
                            if (!pubOk) {
                                err = client.getLastError();
                                pushStep("发布数据", "fail", err, (long)std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now()-pubStart).count());
                            } else {
                                std::unique_lock<std::mutex> lk(mtx);
                                cv.wait_for(lk, std::chrono::seconds(3));
                                pushStep("发布数据", "ok", std::string("主题：") + dataTopic, (long)std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now()-pubStart).count());
                            }
                            // 记录发送数据消息
                            std::string payloadPreview = data.str();
                            pushMsg("send", dataTopic, payloadPreview);
                        }

                        if (testMode == "full") {
                            // 应用层登录与同步（简化验证）
                            auto loginStart = std::chrono::steady_clock::now();
                            std::string loginTopic = "up/gateway/login";
                            std::ostringstream loginPayload;
                            loginPayload << "{\\\"cmdId\\\":101,\\\"seq\\\":0,\\\"gwId\\\":\\\"" << (deviceSn.empty()? clientId : deviceSn)
                                         << "\\\",\\\"errorCode\\\":0,\\\"data\\\":{\\\"deviceType\\\":" << (deviceType.empty()?"0":deviceType)
                                         << ",\\\"random\\\":1234}}";
                            std::string loginPayloadStr = loginPayload.str();
                            bool loginOk = client.publish(loginTopic, loginPayloadStr, q, false);
                            pushStep("发布登录", loginOk?"ok":"fail", std::string("主题：") + loginTopic, (long)std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now()-loginStart).count());
                            pushMsg("send", loginTopic, loginPayloadStr);

                            auto subStart = std::chrono::steady_clock::now();
                            std::string downSync = "down/gateway/" + (deviceSn.empty()? clientId : deviceSn) + "/sync";
                            bool subOk = client.subscribe(downSync, q);
                            pushStep("订阅同步下行", subOk?"ok":"fail", std::string("主题：") + downSync, (long)std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now()-subStart).count());

                            auto syncStart = std::chrono::steady_clock::now();
                            std::string upSyncRes = "up/gateway/sync/res";
                            std::string syncPayload = std::string("{\\\"cmdId\\\":201,\\\"gwId\\\":\\\"") + (deviceSn.empty()? clientId : deviceSn) + "\\\",\\\"ok\\\":true}";
                            bool syncOk = client.publish(upSyncRes, syncPayload, q, false);
                            pushStep("发布同步响应", syncOk?"ok":"fail", std::string("主题：") + upSyncRes, (long)std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now()-syncStart).count());
                            pushMsg("send", upSyncRes, syncPayload);
                        } else if (testMode == "register_login") {
                            // 动态注册：构造注册 CONNECT 参数
                            if (signMethod != "hmacsha256" || deviceTypeSecret.empty()) {
                                pushStep("动态注册", "fail", "缺少签名算法或设备类型密钥", 0);
                            } else {
                                auto regStart = std::chrono::steady_clock::now();
                                std::string random = randomDigits(6);
                                std::string content = std::string("authTyperegister") +
                                                      "deviceSn" + deviceSn +
                                                      "deviceType" + deviceType +
                                                      "random" + random +
                                                      "securemode" + "1";
                                std::string regPwd = hmacSha256Hex(deviceTypeSecret, content);
                                std::string regCid = deviceType + "." + deviceSn +
                                                     "|securemode=1,authType=register,random=" + random + ",signMethod=hmacsha256";
                                std::string regUser = deviceSn + "&" + deviceType;

                                // 重新初始化客户端以便使用新的 clientId
                                client.stopLoop();
                                client.disconnect();
                                client.initialize(regCid, host, port);
                                client.setKeepAlive(keepAlive);
                                client.setCleanSession(cleanSession);
                                if (useTls) client.setTLS(caFile, certFile, keyFile);
                                client.setCredentials(regUser, regPwd);

                                std::string registerTopic = "/ext/" + deviceSn + "/register";
                                std::string deviceSecret;
                                std::mutex m2; std::condition_variable cv2; bool gotReg = false;
                                client.setMessageCallback([&](const std::string& topic, const std::string& payload){
                                    if (topic == registerTopic) {
                                        ems::utils::Json js;
                                        if (js.parse(payload)) {
                                            deviceSecret = js.getString("deviceSecret");
                                            if (!deviceSecret.empty()) {
                                                std::unique_lock<std::mutex> lk(m2); gotReg = true; cv2.notify_all();
                                            }
                                        }
                                        // 记录注册下发
                                        pushMsg("recv", topic, payload);
                                    }
                                });
                                bool regConn = client.connect();
                                if (!regConn) {
                                    pushStep("动态注册", "fail", std::string("连接失败：") + client.getLastError(), (long)std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now()-regStart).count());
                                } else {
                                    client.startLoop();
                                    client.subscribe(registerTopic, ems::protocol::MqttQoS::QOS_1);
                                    {
                                        std::unique_lock<std::mutex> lk(m2);
                                        cv2.wait_for(lk, std::chrono::seconds(5));
                                    }
                                    if (deviceSecret.empty()) {
                                        pushStep("动态注册", "fail", "超时未收到设备密钥", (long)std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now()-regStart).count());
                                    } else {
                                        pushStep("动态注册", "ok", "已获取设备密钥", (long)std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now()-regStart).count());
                                    }
                                    client.stopLoop();
                                    client.disconnect();

                                    // 登录：使用 deviceSecret 生成密码
                                    auto loginStart2 = std::chrono::steady_clock::now();
                                    std::string random2 = randomDigits(6);
                                    std::string content2 = std::string("authTypelogin") +
                                                           "deviceSn" + deviceSn +
                                                           "deviceType" + deviceType +
                                                           "random" + random2 +
                                                           "securemode" + "1";
                                    std::string loginPwd2 = hmacSha256Hex(deviceSecret, content2);
                                    std::string loginCid2 = deviceType + "." + deviceSn +
                                                            "|securemode=1,authType=login,random=" + random2 + ",signMethod=hmacsha256";
                                    std::string loginUser2 = regUser;

                                    client.initialize(loginCid2, host, port);
                                    client.setKeepAlive(keepAlive);
                                    client.setCleanSession(cleanSession);
                                    if (useTls) client.setTLS(caFile, certFile, keyFile);
                                    client.setCredentials(loginUser2, loginPwd2);
                                    if (client.connect()) {
                                        client.startLoop();
                                        pushStep("登录", "ok", "携带签名连接成功", (long)std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now()-loginStart2).count());
                                        // 继续：发布登录、订阅同步、发布同步响应
                                        auto loginPubStart = std::chrono::steady_clock::now();
                                        std::string loginTopic2 = "up/gateway/login";
                                        std::ostringstream loginPayload2;
                                        loginPayload2 << "{\\\"cmdId\\\":101,\\\"seq\\\":0,\\\"gwId\\\":\\\"" << (deviceSn.empty()? clientId : deviceSn)
                                                      << "\\\",\\\"errorCode\\\":0,\\\"data\\\":{\\\"deviceType\\\":" << (deviceType.empty()?"0":deviceType)
                                                      << ",\\\"random\\\":1234}}";
                                        std::string loginPayload2Str = loginPayload2.str();
                                        bool loginOk2 = client.publish(loginTopic2, loginPayload2Str, ems::protocol::MqttQoS::QOS_1, false);
                                        pushStep("发布登录", loginOk2?"ok":"fail", std::string("主题：") + loginTopic2, (long)std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now()-loginPubStart).count());
                                        pushMsg("send", loginTopic2, loginPayload2Str);

                                        auto subStart2 = std::chrono::steady_clock::now();
                                        std::string downSync2 = "down/gateway/" + (deviceSn.empty()? clientId : deviceSn) + "/sync";
                                        bool subOk2 = client.subscribe(downSync2, ems::protocol::MqttQoS::QOS_1);
                                        pushStep("订阅同步下行", subOk2?"ok":"fail", std::string("主题：") + downSync2, (long)std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now()-subStart2).count());

                                        auto syncStart2 = std::chrono::steady_clock::now();
                                        std::string upSyncRes2 = "up/gateway/sync/res";
                                        std::string syncPayload2 = std::string("{\\\"cmdId\\\":201,\\\"gwId\\\":\\\"") + (deviceSn.empty()? clientId : deviceSn) + "\\\",\\\"ok\\\":true}";
                                        bool syncOk2 = client.publish(upSyncRes2, syncPayload2, ems::protocol::MqttQoS::QOS_1, false);
                                        pushStep("发布同步响应", syncOk2?"ok":"fail", std::string("主题：") + upSyncRes2, (long)std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now()-syncStart2).count());
                                        pushMsg("send", upSyncRes2, syncPayload2);
                                        // 接下来统一的发布数据与断开
                                    } else {
                                        pushStep("登录", "fail", client.getLastError(), (long)std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now()-loginStart2).count());
                                    }
                                }
                            }
                        }
                    } else {
                        err = "MQTT CONNECT not confirmed";
                        pushStep("连接", "fail", err, 0);
                    }
                    auto discStart = std::chrono::steady_clock::now();
                    client.stopLoop();
                    client.disconnect();
                    pushStep("断开连接", "ok", "已关闭", (long)std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now()-discStart).count());
                }
            } else {
                err = "MQTT init failed";
                pushStep("初始化", "fail", err, (long)std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now()-stepStart).count());
            }
        } else {
            err = "Host or port missing";
            pushStep("准备", "fail", "主机或端口缺失", 0);
        }

        auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(
            std::chrono::steady_clock::now() - start).count();

        std::ostringstream payload;
        payload << "{\"success\":" << ((hadConnected && published) ? "true" : "false")
                << ",\"message\":\"" << ((hadConnected && published) ? "连接并上报成功" : err) << "\"," 
                << "\"details\":{\"responseTime\":" << elapsed
                << ",\"connected\":" << (hadConnected?"true":"false")
                << ",\"published\":" << (published?"true":"false")
                << ",\"topic\":\"" << dataTopic << "\""
                << ",\"protocol\":\"" << (useTls ? "MQTTS" : "MQTT") << "\"},\"timeline\":[" << timeline.str() << "],\"messages\":[" << msglog.str() << "]}";

        std::string content = payload.str();
        std::ostringstream response;
        response << "HTTP/1.1 200 OK\r\n"
                 << "Content-Type: application/json\r\n"
                 << "Access-Control-Allow-Origin: *\r\n"
                 << "Content-Length: " << content.length() << "\r\n\r\n"
                 << content;
        return response.str();
    }

    // /api/config/modbus -> GET returns config/modbus.conf JSON, PUT writes it
    if (path == "/api/config/modbus") {
        if (method == "GET") {
            // Read file content
            std::ifstream inFile(configRoot_ + "/modbus.conf", std::ios::in | std::ios::binary);
            std::string content;
            if (inFile) {
                std::ostringstream ss;
                ss << inFile.rdbuf();
                content = ss.str();
            } else {
                // Fallback: try modbus.json if modbus.conf missing
                std::ifstream altFile(configRoot_ + "/modbus.json", std::ios::in | std::ios::binary);
                if (altFile) {
                    std::ostringstream ss;
                    ss << altFile.rdbuf();
                    content = ss.str();
                } else {
                    content = "{}";
                }
            }

            // 为了便于可读性，返回格式化后的JSON
            content = prettyJson(content);

            std::ostringstream response;
            response << "HTTP/1.1 200 OK\r\n"
                     << "Content-Type: application/json\r\n"
                     << "Access-Control-Allow-Origin: *\r\n"
                     << "Content-Length: " << content.length() << "\r\n\r\n"
                     << content;
            return response.str();
        } else if (method == "PUT") {
            std::string body = extractBody(rawRequest);
            // Very basic validation: must look like JSON object
            auto firstBrace = body.find('{');
            auto lastBrace = body.rfind('}');
            if (firstBrace == std::string::npos || lastBrace == std::string::npos || lastBrace < firstBrace) {
                std::string msg = "{\"error\":\"Invalid JSON payload\"}";
                std::ostringstream response;
                response << "HTTP/1.1 400 Bad Request\r\n"
                         << "Content-Type: application/json\r\n"
                         << "Access-Control-Allow-Origin: *\r\n"
                         << "Content-Length: " << msg.length() << "\r\n\r\n"
                         << msg;
                return response.str();
            }

            // Write to modbus.conf
            // 先格式化再写入，确保文件可读
            std::string pretty = prettyJson(body);

            std::ofstream outFile(configRoot_ + "/modbus.conf", std::ios::out | std::ios::binary | std::ios::trunc);
            if (!outFile) {
                std::string msg = "{\"error\":\"Failed to open modbus.conf for writing\"}";
                std::ostringstream response;
                response << "HTTP/1.1 500 Internal Server Error\r\n"
                         << "Content-Type: application/json\r\n"
                         << "Access-Control-Allow-Origin: *\r\n"
                         << "Content-Length: " << msg.length() << "\r\n\r\n"
                         << msg;
                return response.str();
            }
            outFile.write(pretty.data(), static_cast<std::streamsize>(pretty.size()));
            outFile.close();

            std::string msg = "{\"message\":\"Modbus配置更新成功\"}";
            std::ostringstream response;
            response << "HTTP/1.1 200 OK\r\n"
                     << "Content-Type: application/json\r\n"
                     << "Access-Control-Allow-Origin: *\r\n"
                     << "Content-Length: " << msg.length() << "\r\n\r\n"
                     << msg;
            return response.str();
        }
    }

    // /api/config/devices -> GET returns config/devices.json, PUT writes it
    if (path == "/api/config/devices") {
        if (method == "GET") {
            // Read file content
            std::ifstream inFile(configRoot_ + "/devices.json", std::ios::in | std::ios::binary);
            std::string content;
            if (inFile) {
                std::ostringstream ss;
                ss << inFile.rdbuf();
                content = ss.str();
            } else {
                content = "{\"devices\":[]}";
            }

            // 为了便于可读性，返回格式化后的JSON
            content = prettyJson(content);

            std::ostringstream response;
            response << "HTTP/1.1 200 OK\r\n"
                     << "Content-Type: application/json\r\n"
                     << "Access-Control-Allow-Origin: *\r\n"
                     << "Content-Length: " << content.length() << "\r\n\r\n"
                     << content;
            return response.str();
        } else if (method == "PUT") {
            std::string body = extractBody(rawRequest);
            // Very basic validation: must look like JSON object
            auto firstBrace = body.find('{');
            auto lastBrace = body.rfind('}');
            if (firstBrace == std::string::npos || lastBrace == std::string::npos || lastBrace < firstBrace) {
                std::string msg = "{\"error\":\"Invalid JSON payload\"}";
                std::ostringstream response;
                response << "HTTP/1.1 400 Bad Request\r\n"
                         << "Content-Type: application/json\r\n"
                         << "Access-Control-Allow-Origin: *\r\n"
                         << "Content-Length: " << msg.length() << "\r\n\r\n"
                         << msg;
                return response.str();
            }

            // Write to devices.json
            // 先格式化再写入，确保文件可读
            std::string pretty = prettyJson(body);

            std::ofstream outFile(configRoot_ + "/devices.json", std::ios::out | std::ios::binary | std::ios::trunc);
            if (!outFile) {
                LOG_ERROR("[WebServer] Failed to open devices.json for writing: " << configRoot_ << "/devices.json");
                std::string msg = "{\"error\":\"Failed to open devices.json for writing\"}";
                std::ostringstream response;
                response << "HTTP/1.1 500 Internal Server Error\r\n"
                         << "Content-Type: application/json\r\n"
                         << "Access-Control-Allow-Origin: *\r\n"
                         << "Content-Length: " << msg.length() << "\r\n\r\n"
                         << msg;
                return response.str();
            }
            outFile.write(pretty.data(), static_cast<std::streamsize>(pretty.size()));
            outFile.close();
            LOG_INFO("[WebServer] devices.json updated successfully, bytes written=" << pretty.size());

            auto& cfg = ems::utils::Config::getInstance();
            if (!cfg.reloadDevices()) {
                std::string msg = "{\"error\":\"Failed to reload device configuration\"}";
                std::ostringstream response;
                response << "HTTP/1.1 500 Internal Server Error\r\n"
                         << "Content-Type: application/json\r\n"
                         << "Access-Control-Allow-Origin: *\r\n"
                         << "Content-Length: " << msg.length() << "\r\n\r\n"
                         << msg;
                return response.str();
            }

            if (deviceManager_) {
                if (!deviceManager_->reloadDevices()) {
                    std::string msg = "{\"error\":\"Failed to apply device configuration to runtime\"}";
                    std::ostringstream response;
                    response << "HTTP/1.1 500 Internal Server Error\r\n"
                             << "Content-Type: application/json\r\n"
                             << "Access-Control-Allow-Origin: *\r\n"
                             << "Content-Length: " << msg.length() << "\r\n\r\n"
                             << msg;
                    return response.str();
                }
            }

            if (dataUploader_) {
                dataUploader_->rebuildDeviceIntervals();
            }

            std::string msg = "{\"message\":\"设备配置更新成功\"}";
            std::ostringstream response;
            response << "HTTP/1.1 200 OK\r\n"
                     << "Content-Type: application/json\r\n"
                     << "Access-Control-Allow-Origin: *\r\n"
                     << "Content-Length: " << msg.length() << "\r\n\r\n"
                     << msg;
            return response.str();
        }
    }

    // /api/protocols -> GET returns list of available protocols
    if (path == "/api/protocols") {
        if (method == "GET") {
            struct ProtocolOption {
                std::string relativePath;
                std::string displayLabel;
                std::vector<std::string> categories;
            };
            std::vector<ProtocolOption> protocolOptions;

            // Scan protocols directory for JSON files
            std::string protocolsDir = configRoot_ + "/protocols";

            auto extractCategories = [&](const std::string& fullPath) {
                std::set<std::string> categorySet;
                std::ifstream jsonFile(fullPath);
                if (jsonFile.is_open()) {
                    ems::utils::Json json;
                    if (json.parse(jsonFile)) {
                        auto dataPoints = json.getArray("data_points");
                        for (const auto& point : dataPoints) {
                            std::string cat = point.getString("device_category");
                            if (!cat.empty()) {
                                categorySet.insert(cat);
                            }
                        }
                    }
                }
                return std::vector<std::string>(categorySet.begin(), categorySet.end());
            };

            auto buildDisplayName = [](const std::string& path) {
                std::string displayName = path;
                if (displayName.find("zkzf") != std::string::npos) {
                    return std::string("中科智赋 (zkzf)");
                } else if (displayName.find("monet") != std::string::npos) {
                    return std::string("Monet IoT模板");
                } else if (displayName.find("template") != std::string::npos) {
                    return std::string("通用协议模板");
                } else if (displayName.size() > 5 && displayName.substr(displayName.size() - 5) == ".json") {
                    return displayName.substr(0, displayName.size() - 5);
                }
                return displayName;
            };

            auto nameFromPath = [](const std::string& relativePath) {
                std::string clean = relativePath;
                std::replace(clean.begin(), clean.end(), '/', ' ');
                std::replace(clean.begin(), clean.end(), '_', ' ');
                if (clean.size() > 5 && clean.substr(clean.size() - 5) == ".json") {
                    clean = clean.substr(0, clean.size() - 5);
                }
                clean.erase(clean.begin(), std::find_if(clean.begin(), clean.end(), [](unsigned char ch){ return !std::isspace(ch); }));
                clean.erase(std::find_if(clean.rbegin(), clean.rend(), [](unsigned char ch){ return !std::isspace(ch); }).base(), clean.end());
                for (auto& ch : clean) {
                    if (ch == '-') ch = ' ';
                }
                return clean.empty() ? relativePath : clean;
            };

            // Recursively scan protocols directory without using <filesystem>
            std::function<void(const std::string&)> scanDirectory;
            scanDirectory = [&](const std::string& relativePath) {
                std::string basePath = protocolsDir + (relativePath.empty() ? "" : "/" + relativePath);
                DIR* dir = opendir(basePath.c_str());
                if (!dir) return;
                struct dirent* entry;
                while ((entry = readdir(dir)) != nullptr) {
                    std::string name = entry->d_name;
                    if (name == "." || name == "..") continue;
                    std::string childRelative = relativePath.empty() ? name : relativePath + "/" + name;
                    std::string childFullPath = protocolsDir + "/" + childRelative;
                    struct stat pathStat{};
                    if (::stat(childFullPath.c_str(), &pathStat) != 0) {
                        continue;
                    }
                    if (S_ISDIR(pathStat.st_mode)) {
                        scanDirectory(childRelative);
                    } else if (S_ISREG(pathStat.st_mode)) {
                        if (childRelative.size() >= 5 && childRelative.substr(childRelative.size() - 5) == ".json") {
                            ProtocolOption option;
                            option.relativePath = childRelative;
                            option.displayLabel = nameFromPath(childRelative);
                            option.categories = extractCategories(childFullPath);
                            protocolOptions.push_back(option);
                        }
                    }
                }
                closedir(dir);
            };

            scanDirectory("");

            // Build JSON response
            std::ostringstream jsonBuilder;
            jsonBuilder << "{\n  \"protocols\": [\n";
            for (size_t i = 0; i < protocolOptions.size(); ++i) {
                jsonBuilder << "    {\n";
                jsonBuilder << "      \"value\": \"config/protocols/" << protocolOptions[i].relativePath << "\",\n";
                jsonBuilder << "      \"label\": \"" << protocolOptions[i].displayLabel << "\",\n";
                jsonBuilder << "      \"categories\": [";
                for (size_t ci = 0; ci < protocolOptions[i].categories.size(); ++ci) {
                    jsonBuilder << "\"" << protocolOptions[i].categories[ci] << "\"";
                    if (ci + 1 < protocolOptions[i].categories.size()) {
                        jsonBuilder << ",";
                    }
                }
                jsonBuilder << "]\n";
                jsonBuilder << "    }";
                if (i < protocolOptions.size() - 1) {
                    jsonBuilder << ",";
                }
                jsonBuilder << "\n";
            }
            jsonBuilder << "  ]\n}";

            std::string content = jsonBuilder.str();
            std::ostringstream response;
            response << "HTTP/1.1 200 OK\r\n"
                     << "Content-Type: application/json\r\n"
                     << "Access-Control-Allow-Origin: *\r\n"
                     << "Content-Length: " << content.length() << "\r\n\r\n"
                     << content;
            return response.str();
        }
    }

    if (path == "/api/modbus/serial-ports") {
        if (method != "GET") {
            std::string msg = "{\"error\":\"Method not allowed\"}";
            std::ostringstream response;
            response << "HTTP/1.1 405 Method Not Allowed\r\n"
                     << "Content-Type: application/json\r\n"
                     << "Access-Control-Allow-Origin: *\r\n"
                     << "Content-Length: " << msg.length() << "\r\n\r\n"
                     << msg;
            return response.str();
        }

        auto ports = listSerialPorts();
        std::ostringstream payload;
        payload << "{\n  \"ports\": [";
        for (size_t i = 0; i < ports.size(); ++i) {
            payload << "\"" << ports[i] << "\"";
            if (i + 1 < ports.size()) {
                payload << ", ";
            }
        }
        payload << "],\n  \"count\": " << ports.size() << ",\n  \"timestamp\": \"" << getCurrentTimestamp() << "\"\n}";

        std::string content = payload.str();
        std::ostringstream response;
        response << "HTTP/1.1 200 OK\r\n"
                 << "Content-Type: application/json\r\n"
                 << "Access-Control-Allow-Origin: *\r\n"
                 << "Content-Length: " << content.length() << "\r\n\r\n"
                 << content;
        return response.str();
    }

    // /api/modbus/test -> POST
    // 测试Modbus设备连接
    if (path == "/api/modbus/test") {
        if (method != "POST") {
            std::string msg = "{\"error\":\"Method not allowed\"}";
            std::ostringstream response;
            response << "HTTP/1.1 405 Method Not Allowed\r\n"
                     << "Content-Type: application/json\r\n"
                     << "Access-Control-Allow-Origin: *\r\n"
                     << "Content-Length: " << msg.length() << "\r\n\r\n"
                     << msg;
            return response.str();
        }

        std::string body = extractBody(rawRequest);
        ems::utils::Json posted;
        std::string deviceType = "tcp";
        std::string address = "";
        int port = 502;
        int slaveId = 1;
        int timeout = 3000;
        
        // RTU特有参数
        std::string parity = "N";
        int dataBits = 8;
        int stopBits = 1;
        int baudrate = 9600;

        if (!body.empty() && posted.parse(body)) {
            deviceType = posted.getString("type", deviceType);
            address = posted.getString("address", address);
            port = posted.getInt("port", port);
            slaveId = posted.getInt("slave_id", slaveId);
            timeout = posted.getInt("timeout", timeout);
            
            if (deviceType == "rtu") {
                parity = posted.getString("parity", parity);
                dataBits = posted.getInt("data_bits", dataBits);
                stopBits = posted.getInt("stop_bits", stopBits);
                baudrate = posted.getInt("baudrate", baudrate);
            }
        }

        auto start = std::chrono::steady_clock::now();
        bool connected = false;
        bool tested = false;
        std::string err;

        // 模拟Modbus连接测试
        if (!address.empty()) {
            if (deviceType == "tcp") {
                // 简单的TCP连接测试
                std::this_thread::sleep_for(std::chrono::milliseconds(500));
                
                // 简单验证IP地址格式
                if (address.find('.') != std::string::npos && port > 0 && port <= 65535) {
                    connected = true;
                    tested = true;
                } else {
                    err = "Invalid TCP address or port";
                }
            } else if (deviceType == "rtu") {
                // RTU串口测试
                std::this_thread::sleep_for(std::chrono::milliseconds(800));
                
                // 简单验证串口设备名格式
                if (address.find("/dev/") == 0 || address.find("COM") == 0) {
                    connected = true;
                    tested = true;
                } else {
                    err = "Invalid serial device path";
                }
            } else {
                err = "Unsupported device type";
            }
        } else {
            err = "Address is required";
        }

        auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(
            std::chrono::steady_clock::now() - start).count();

        std::ostringstream payload;
        payload << "{\"success\":" << (connected && tested ? "true" : "false")
                << ",\"message\":\"" << (connected && tested ? "Modbus设备连接测试成功" : err) << "\""
                << ",\"details\":{"
                << "\"responseTime\":" << elapsed
                << ",\"connected\":" << (connected ? "true" : "false")
                << ",\"deviceType\":\"" << deviceType << "\""
                << ",\"address\":\"" << address << "\""
                << ",\"port\":" << port
                << ",\"slaveId\":" << slaveId;
        
        if (deviceType == "rtu") {
            payload << ",\"baudrate\":" << baudrate
                    << ",\"parity\":\"" << parity << "\""
                    << ",\"dataBits\":" << dataBits
                    << ",\"stopBits\":" << stopBits;
        }
        
        payload << "}}";

        std::string content = payload.str();
        std::ostringstream response;
        response << "HTTP/1.1 200 OK\r\n"
                 << "Content-Type: application/json\r\n"
                 << "Access-Control-Allow-Origin: *\r\n"
                 << "Content-Length: " << content.length() << "\r\n\r\n"
                 << content;
        return response.str();
    }

    if (path == "/api/system/restart") {
        if (method != "POST") {
            std::string msg = "{\"error\":\"Method not allowed\"}";
            std::ostringstream response;
            response << "HTTP/1.1 405 Method Not Allowed\r\n"
                     << "Content-Type: application/json\r\n"
                     << "Access-Control-Allow-Origin: *\r\n"
                     << "Content-Length: " << msg.length() << "\r\n\r\n"
                     << msg;
            return response.str();
        }

        std::string content = "{\"success\":true,\"message\":\"重启命令已下发\"}";
        std::ostringstream response;
        response << "HTTP/1.1 200 OK\r\n"
                 << "Content-Type: application/json\r\n"
                 << "Access-Control-Allow-Origin: *\r\n"
                 << "Content-Length: " << content.length() << "\r\n\r\n"
                 << content;

        std::string configRootCopy = configRoot_;
        std::thread([configRootCopy]() {
            auto computeProjectRoot = [](std::string configRoot) {
                while (!configRoot.empty() && (configRoot.back() == '/' || configRoot.back() == '\\')) {
                    configRoot.pop_back();
                }
                size_t pos = configRoot.find_last_of("/\\");
                if (pos != std::string::npos) {
                    std::string parent = configRoot.substr(0, pos);
                    if (parent.empty()) {
                        return std::string("/");
                    }
                    return parent;
                }
                return std::string(".");
            };

            std::this_thread::sleep_for(std::chrono::milliseconds(300));
            std::string projectRoot = computeProjectRoot(configRootCopy);
            std::string command = "cd \"" + projectRoot + "\" && ./start.sh --skip-web > /dev/null 2>&1 &";
            std::system(command.c_str());
            std::this_thread::sleep_for(std::chrono::milliseconds(200));
            std::exit(0);
        }).detach();

        return response.str();
    }

    if (path == "/api/dispatch/control") {
        if (!dispatchService_) {
            std::string content = "{\"error\":\"Dispatch service unavailable\"}";
            std::ostringstream response;
            response << "HTTP/1.1 503 Service Unavailable\r\n"
                     << "Content-Type: application/json\r\n"
                     << "Access-Control-Allow-Origin: *\r\n"
                     << "Content-Length: " << content.length() << "\r\n\r\n"
                     << content;
            return response.str();
        }

        if (method == "GET") {
            std::string content = std::string("{\"enabled\": ") + (dispatchService_->isEnabled() ? "true" : "false") + "}";
            std::ostringstream response;
            response << "HTTP/1.1 200 OK\r\n"
                     << "Content-Type: application/json\r\n"
                     << "Access-Control-Allow-Origin: *\r\n"
                     << "Content-Length: " << content.length() << "\r\n\r\n"
                     << content;
            return response.str();
        }

        if (method == "POST") {
            std::string body = extractBody(rawRequest);
            ems::utils::Json json;
            bool enabled = true;
            if (!body.empty() && json.parse(body)) {
                enabled = json.getBool("enabled", true);
            }
            dispatchService_->setEnabled(enabled);
            std::string content = std::string("{\"enabled\": ") + (dispatchService_->isEnabled() ? "true" : "false") + "}";
            std::ostringstream response;
            response << "HTTP/1.1 200 OK\r\n"
                     << "Content-Type: application/json\r\n"
                     << "Access-Control-Allow-Origin: *\r\n"
                     << "Content-Length: " << content.length() << "\r\n\r\n"
                     << content;
            return response.str();
        }

        std::string msg = "{\"error\":\"Method not allowed\"}";
        std::ostringstream response;
        response << "HTTP/1.1 405 Method Not Allowed\r\n"
                 << "Content-Type: application/json\r\n"
                 << "Access-Control-Allow-Origin: *\r\n"
                 << "Content-Length: " << msg.length() << "\r\n\r\n"
                 << msg;
        return response.str();
    }

    if (path == "/api/dispatch/status" && method == "GET") {
        if (!dispatchService_) {
            std::string content = "{\"error\":\"Dispatch service unavailable\"}";
            std::ostringstream response;
            response << "HTTP/1.1 503 Service Unavailable\r\n"
                     << "Content-Type: application/json\r\n"
                     << "Content-Length: " << content.length() << "\r\n"
                     << "Access-Control-Allow-Origin: *\r\n"
                     << "\r\n"
                     << content;
            return response.str();
        }

        const auto ctx = dispatchService_->getLastContext();
        const auto action = dispatchService_->getLastAction();
        const std::string windowStr = service::toString(ctx.storage_window);
        const std::string stateStr = service::toString(ctx.storage_state);

        std::ostringstream json;
        json << std::boolalpha;
        json << "{\n"
             << "  \"context\": {\n"
             << "    \"load_power_kw\": " << ctx.load_power_kw << ",\n"
             << "    \"pv_power_kw\": " << ctx.pv_power_kw << ",\n"
             << "    \"storage_charge_cap_kw\": " << ctx.storage_charge_cap_kw << ",\n"
             << "    \"storage_discharge_cap_kw\": " << ctx.storage_discharge_cap_kw << ",\n"
             << "    \"storage_window\": \"";
        appendEscaped(json, windowStr);
        json << "\",\n"
             << "    \"storage_state\": \"";
        appendEscaped(json, stateStr);
        json << "\",\n"
             << "    \"storage_soc\": " << ctx.storage_soc << ",\n"
             << "    \"storage_charge_price\": " << ctx.storage_charge_price << ",\n"
             << "    \"pv_feed_price\": " << ctx.pv_feed_price << ",\n"
             << "    \"grid_price_now\": " << ctx.grid_price_now << ",\n"
             << "    \"grid_price_peak\": " << ctx.grid_price_peak << ",\n"
             << "    \"is_peak_period\": " << (ctx.is_peak_period ? "true" : "false") << ",\n"
             << "    \"critical_load_kw\": " << ctx.critical_load_kw << "\n"
             << "  },\n"
             << "  \"actions\": [\n";

        for (size_t i = 0; i < action.actions.size(); ++i) {
            json << "    \"";
            appendEscaped(json, service::toString(action.actions[i]));
            json << "\"";
            if (i + 1 < action.actions.size()) {
                json << ",";
            }
            json << "\n";
        }

        json << "  ],\n"
             << "  \"trace\": [\n";

        for (size_t i = 0; i < action.trace.size(); ++i) {
            json << "    \"";
            appendEscaped(json, action.trace[i]);
            json << "\"";
            if (i + 1 < action.trace.size()) {
                json << ",";
            }
            json << "\n";
        }

        json << "  ],\n"
             << "  \"storage_device_command\": \"";
        appendEscaped(json, service::toString(action.storageCommand));
        json << "\",\n"
             << "  \"enabled\": " << (dispatchService_->isEnabled() ? "true" : "false") << "\n"
             << "}";

        std::string content = json.str();
        std::ostringstream response;
        response << "HTTP/1.1 200 OK\r\n"
                 << "Content-Type: application/json\r\n"
                 << "Content-Length: " << content.length() << "\r\n"
                 << "Access-Control-Allow-Origin: *\r\n"
                 << "\r\n"
                 << content;
        return response.str();
    }

    if (path == "/api/status" && method == "GET") {
        const long uptimeSeconds = getSystemUptime();
        MemoryUsage memInfo = queryMemoryUsage();
        if (!memInfo.valid) {
            memInfo.totalBytes = 0;
            memInfo.usedBytes = 0;
            memInfo.percentage = 0.0;
        }

        double cpuUsage = queryCpuUsagePercent();
        if (!std::isfinite(cpuUsage) || cpuUsage < 0.0) {
            cpuUsage = 0.0;
        }
        if (cpuUsage > 100.0) {
            cpuUsage = 100.0;
        }

        size_t totalDevices = 0;
        size_t onlineDevices = 0;
        size_t errorDevices = 0;
        if (deviceManager_) {
            auto devices = deviceManager_->getAllDevices();
            for (auto* device : devices) {
                if (!device || !device->isEnabled()) {
                    continue;
                }
                ++totalDevices;
                const auto status = device->getStatus();
                if (status == device::DeviceStatus::CONNECTED) {
                    ++onlineDevices;
                } else if (status == device::DeviceStatus::ERROR) {
                    ++errorDevices;
                }
            }
        }
        size_t offlineDevices = (totalDevices > onlineDevices) ? (totalDevices - onlineDevices) : 0;

        bool mqttConnected = false;
        uint64_t mqttSent = 0;
        uint64_t mqttFailed = 0;
        long long lastPublishMs = 0;
        if (dataUploader_) {
            mqttConnected = dataUploader_->isMqttConnected();
            mqttSent = dataUploader_->getMessagesSent();
            mqttFailed = dataUploader_->getMessagesFailed();
            lastPublishMs = dataUploader_->getLastPublishTimestampMs();
        }

        uint64_t totalCollections = 0;
        uint64_t successfulCollections = 0;
        uint64_t failedCollections = 0;
        uint64_t lastCollectionTs = 0;
        uint64_t totalPointsCollected = 0;
        if (dataCollector_) {
            totalCollections = dataCollector_->getTotalCollections();
            successfulCollections = dataCollector_->getSuccessfulCollections();
            failedCollections = dataCollector_->getFailedCollections();
            lastCollectionTs = dataCollector_->getLastSuccessfulCollectionTs();
            totalPointsCollected = dataCollector_->getTotalPointsCollected();
        }
        const uint64_t attemptCount = successfulCollections + failedCollections;
        double successRate = 0.0;
        if (attemptCount > 0) {
            successRate = (static_cast<double>(successfulCollections) / static_cast<double>(attemptCount)) * 100.0;
        }
        if (!std::isfinite(successRate) || successRate < 0.0) {
            successRate = 0.0;
        }
        if (successRate > 100.0) {
            successRate = 100.0;
        }

        std::ostringstream json;
        json << "{";
        json << "\"uptime\":" << uptimeSeconds << ",";
        json << "\"memory\":{"
             << "\"used\":" << memInfo.usedBytes << ","
             << "\"total\":" << memInfo.totalBytes << ","
             << "\"percentage\":" << formatDouble(memInfo.percentage, 1)
             << "},";
        json << "\"cpu\":{"
             << "\"usage\":" << formatDouble(cpuUsage, 1)
             << "},";
        json << "\"devices\":{"
             << "\"total\":" << totalDevices << ","
             << "\"online\":" << onlineDevices << ","
             << "\"offline\":" << offlineDevices << ","
             << "\"error\":" << errorDevices
             << "},";
        json << "\"mqtt\":{"
             << "\"connected\":" << (mqttConnected ? "true" : "false") << ","
             << "\"lastPublish\":" << (lastPublishMs > 0 ? std::to_string(lastPublishMs) : "null") << ","
             << "\"messagesSent\":" << mqttSent << ","
             << "\"messagesFailed\":" << mqttFailed
             << "},";
        json << "\"dataCollection\":{"
             << "\"totalPoints\":" << totalPointsCollected << ","
             << "\"successRate\":" << formatDouble(successRate, 1) << ","
             << "\"lastCollection\":" << (lastCollectionTs > 0 ? std::to_string(lastCollectionTs) : "null")
             << "},";

        json << "\"dataPoints\":[";
        bool firstPoint = true;
        if (dataUploader_) {
            struct PointRow {
                std::string deviceId;
                std::string category;
                std::string key;
                double value;
                std::string unit;
                uint64_t timestamp;
            };
            std::vector<PointRow> points;
            auto snapshot = dataUploader_->getSnapshotCopy();
            for (const auto& entry : snapshot) {
                const std::string& deviceId = entry.first;
                for (const auto& value : entry.second) {
                    if (!value.valid) {
                        continue;
                    }
                    PointRow row;
                    row.deviceId = deviceId;
                    row.category = value.deviceCategory.empty() ? "unknown" : value.deviceCategory;
                    row.key = value.key;
                    row.value = value.value;
                    row.unit = value.unit;
                    row.timestamp = value.timestamp;
                    points.emplace_back(std::move(row));
                }
            }
            std::sort(points.begin(), points.end(), [](const PointRow& a, const PointRow& b) {
                if (a.deviceId != b.deviceId) return a.deviceId < b.deviceId;
                if (a.category != b.category) return a.category < b.category;
                if (a.key != b.key) return a.key < b.key;
                return a.timestamp < b.timestamp;
            });

            for (const auto& row : points) {
                if (!firstPoint) {
                    json << ",";
                }
                firstPoint = false;
                json << "{";
                json << "\"deviceId\":\""; appendEscaped(json, row.deviceId); json << "\",";
                json << "\"category\":\""; appendEscaped(json, row.category); json << "\",";
                json << "\"key\":\""; appendEscaped(json, row.key); json << "\",";
                json << "\"value\":" << formatDouble(row.value, 3) << ",";
                if (row.unit.empty()) {
                    json << "\"unit\":null,";
                } else {
                    json << "\"unit\":\""; appendEscaped(json, row.unit); json << "\",";
                }
                json << "\"timestamp\":" << row.timestamp;
                json << "}";
            }
        }
        json << "]";
        json << "}";

        std::string content = json.str();
        std::ostringstream response;
        response << "HTTP/1.1 200 OK\r\n"
                 << "Content-Type: application/json\r\n"
                 << "Content-Length: " << content.length() << "\r\n"
                 << "Access-Control-Allow-Origin: *\r\n"
                 << "\r\n"
                 << content;
        return response.str();
    }
    
    // 404 for unknown API endpoints
    std::string content = "{\"error\": \"API endpoint not found\"}";
    std::ostringstream response;
    response << "HTTP/1.1 404 Not Found\r\n"
             << "Content-Type: application/json\r\n"
             << "Content-Length: " << content.length() << "\r\n"
             << "Access-Control-Allow-Origin: *\r\n"
             << "\r\n"
             << content;
    return response.str();
}

std::string WebServer::handleWebSocketRequest(const std::string& request) {
    // 简单的WebSocket握手响应
    // 在实际实现中，这里需要处理WebSocket协议升级
    // 现在先返回一个基本的HTTP响应，让前端知道服务器在运行
    return "HTTP/1.1 426 Upgrade Required\r\n"
           "Content-Type: text/plain\r\n"
           "Content-Length: 33\r\n"
           "\r\n"
           "WebSocket upgrade not supported";
}

long WebServer::getSystemUptime() {
    auto now = std::chrono::steady_clock::now();
    auto elapsed = std::chrono::duration_cast<std::chrono::seconds>(now - startTime_);
    return static_cast<long>(elapsed.count());
}

std::string WebServer::getCurrentTimestamp() {
    // 简单实现：返回当前时间戳字符串
    time_t now = time(0);
    char buffer[20];
    strftime(buffer, sizeof(buffer), "%Y-%m-%d %H:%M:%S", localtime(&now));
    return std::string(buffer);
}

} // namespace web
} // namespace ems
